<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>QeePHP 快速入门</title>
<link href="css/base.css" rel="stylesheet" type="text/css">
</head>
<body>

<div id="page">


<div class="guide-section">

  <div class="guide-header">
    <span class="nav">
      <a href="http://qeephp.com/docs/">文档索引</a>
      &raquo;
      <a href="index.html">QeePHP 快速入门</a>
      &raquo;
      <a href="node-users.html">实现用户功能</a>
      &raquo;
      用户登录和注销    </span>
  </div>

  <div class="guide-section-details formatted">

    
<h1>用户登录和注销</h1>

<p>通过前面实现用户注册功能，我们了解了如何完善模型及创建表单。这一个小节我们来实现用户的登录和注销功能。</p>

<h2>实现登录</h2>

<p>修改 users 控制器，加入下列代码：</p>

<pre class="php code"><span class="kw2">function</span> actionLogin<span
class="br0">&#40;</span><span class="br0">&#41;</span>
<span
class="br0">&#123;</span>
    <span class="re0">$form</span> <span
class="sy0">=</span> <span class="kw2">new</span> Form_UserLogin<span
class="br0">&#40;</span>url<span class="br0">&#40;</span><span
class="st_h">'users/login'</span><span class="br0">&#41;</span><span
class="br0">&#41;</span><span class="sy0">;</span>
    <span
class="re0">$form</span><span class="sy0">-&gt;</span>_subject <span
class="sy0">=</span> <span class="st_h">'登录'</span><span
class="sy0">;</span>
&nbsp;
    <span class="kw1">if</span> <span
class="br0">&#40;</span><span class="re0">$this</span><span
class="sy0">-&gt;</span>_context<span class="sy0">-&gt;</span><span
class="me1">isPOST</span><span class="br0">&#40;</span><span
class="br0">&#41;</span> <span class="sy0">&amp;&amp;</span> <span
class="re0">$form</span><span class="sy0">-&gt;</span><span
class="me1">validate</span><span class="br0">&#40;</span><span
class="re0">$_POST</span><span class="br0">&#41;</span><span
class="br0">&#41;</span>
    <span
class="br0">&#123;</span>
        try
        <span
class="br0">&#123;</span>
            <span
class="co1">// 使用 acluser 插件的 validateLogin() 方法验证登录并取得有效的 user 对象</span>
            <span
class="re0">$user</span> <span class="sy0">=</span> User<span
class="sy0">::</span><span class="me2">meta</span><span
class="br0">&#40;</span><span class="br0">&#41;</span><span
class="sy0">-&gt;</span><span class="me1">validateLogin</span><span
class="br0">&#40;</span><span class="re0">$form</span><span
class="br0">&#91;</span><span class="st_h">'username'</span><span
class="br0">&#93;</span><span class="sy0">-&gt;</span><span
class="me1">value</span><span class="sy0">,</span> <span
class="re0">$form</span><span class="br0">&#91;</span><span
class="st_h">'password'</span><span class="br0">&#93;</span><span
class="sy0">-&gt;</span><span class="me1">value</span><span
class="br0">&#41;</span><span class="sy0">;</span>
            <span
class="co1">// 将登录用户的信息存入 SESSION，以便应用程序记住用户的登录状态</span>
            <span
class="re0">$this</span><span class="sy0">-&gt;</span>_app<span
class="sy0">-&gt;</span><span class="me1">changeCurrentUser</span><span
class="br0">&#40;</span><span class="re0">$user</span><span
class="sy0">-&gt;</span><span class="me1">aclData</span><span
class="br0">&#40;</span><span class="br0">&#41;</span><span
class="sy0">,</span> <span class="st_h">'MEMBER'</span><span
class="br0">&#41;</span><span class="sy0">;</span>
            <span
class="co1">// 登录成功后，重定向浏览器</span>
            <span
class="kw1">return</span> <span class="re0">$this</span><span
class="sy0">-&gt;</span>_redirect<span class="br0">&#40;</span>url<span
class="br0">&#40;</span><span class="st_h">'default/index'</span><span
class="br0">&#41;</span><span class="br0">&#41;</span><span
class="sy0">;</span>
        <span class="br0">&#125;</span>
        catch <span
class="br0">&#40;</span>AclUser_UsernameNotFoundException <span
class="re0">$ex</span><span class="br0">&#41;</span>
        <span
class="br0">&#123;</span>
            <span class="re0">$form</span><span
class="br0">&#91;</span><span class="st_h">'username'</span><span
class="br0">&#93;</span><span class="sy0">-&gt;</span><span
class="me1">invalidate</span><span class="br0">&#40;</span><span
class="st0">&quot;您输入的用户名 <span
class="es4">{$form['username']-&gt;value}</span> 不存在&quot;</span><span
class="br0">&#41;</span><span class="sy0">;</span>
        <span
class="br0">&#125;</span>
        catch <span
class="br0">&#40;</span>AclUser_WrongPasswordException <span
class="re0">$ex</span><span class="br0">&#41;</span>
        <span
class="br0">&#123;</span>
            <span class="re0">$form</span><span
class="br0">&#91;</span><span class="st_h">'password'</span><span
class="br0">&#93;</span><span class="sy0">-&gt;</span><span
class="me1">invalidate</span><span class="br0">&#40;</span><span
class="st0">&quot;您输入的密码不正确&quot;</span><span
class="br0">&#41;</span><span class="sy0">;</span>
        <span
class="br0">&#125;</span>
    <span class="br0">&#125;</span>
&nbsp;
    <span
class="re0">$this</span><span class="sy0">-&gt;</span>_view<span
class="br0">&#91;</span><span class="st_h">'form'</span><span
class="br0">&#93;</span> <span class="sy0">=</span> <span
class="re0">$form</span><span class="sy0">;</span>
<span
class="br0">&#125;</span></pre>

<p>这段代码中有两个地方需要说明一下。</p>

<h4>调用行为插件的方法</h4>

<p>acluser 行为插件为模型扩展了多个方法，包括：</p>

<table>
	<thead>
		<tr>
			<th>方法名</th>

			<th>用途</th>

			<th>类型</th>
		</tr>
	</thead>

	<tbody>
		<tr>
			<th>validateLogin()</th>

			<td>验证用户登录并返回用户对象</td>

			<td>类方法</td>
		</tr>

		<tr>
			<th>validateUsername()</th>

			<td>验证用户名</td>

			<td>类方法</td>
		</tr>

		<tr>
			<th>validatePassword()</th>

			<td>验证用户名和密码</td>

			<td>类方法</td>
		</tr>

		<tr>
			<th>changePassword()</th>

			<td>修改指定用户的密码</td>

			<td>类方法</td>
		</tr>

		<tr>
			<th>checkPassword()</th>

			<td>检查指定的密码是否与当前用户的密码相符</td>

			<td>实例方法</td>
		</tr>

		<tr>
			<th>changePassword()</th>

			<td>修改当前用户的密码</td>

			<td>实例方法</td>
		</tr>

		<tr>
			<th>updateLogin()</th>

			<td>更新用户的登录信息</td>

			<td>实例方法</td>
		</tr>

		<tr>
			<th>aclRoles()</th>

			<td>获得包含用户所有角色名的数组</td>

			<td>实例方法</td>
		</tr>

		<tr>
			<th>aclData()</th>

			<td>获得用户的 ACL 数据</td>

			<td>实例方法</td>
		</tr>
	</tbody>
</table>

<p><strong>类方法</strong>是指调用该方法不需要构造模型对象实例，而<strong>实例方法</strong>则只能通过模型对象实例来调用。</p>

<p>要调用一个类方法：</p>

<pre class="php code">User<span class="sy0">::</span><span
class="me2">meta</span><span class="br0">&#40;</span><span
class="br0">&#41;</span><span class="sy0">-&gt;</span><span
class="me1">validateLogin</span><span class="br0">&#40;</span><span
class="br0">&#41;</span><span class="sy0">;</span></pre>

<p>调用一个实例方法则是：</p>

<pre class="php code"><span class="re0">$user</span><span
class="sy0">-&gt;</span><span class="me1">changePassword</span><span
class="br0">&#40;</span><span class="br0">&#41;</span><span
class="sy0">;</span></pre>

<p>可以看出，类方法必须通过 meta()
方法返回的连贯接口来调用。</p>

<p>在用户登录中，我们用到了 acluser 插件提供的 validateLogin()
类方法，所以调用方式就是：</p>

<pre class="php code"><span class="re0">$user</span> <span
class="sy0">=</span> User<span class="sy0">::</span><span
class="me2">meta</span><span class="br0">&#40;</span><span
class="br0">&#41;</span><span class="sy0">-&gt;</span><span
class="me1">validateLogin</span><span class="br0">&#40;</span><span
class="re0">$form</span><span class="br0">&#91;</span><span
class="st_h">'username'</span><span class="br0">&#93;</span><span
class="sy0">-&gt;</span><span class="me1">value</span><span
class="sy0">,</span> <span class="re0">$form</span><span
class="br0">&#91;</span><span class="st_h">'password'</span><span
class="br0">&#93;</span><span class="sy0">-&gt;</span><span
class="me1">value</span><span class="br0">&#41;</span><span
class="sy0">;</span></pre>

<p>validateLogin()
方法检查用户名和密码的正确性，如果检查通过就返回有效的用户对象，否则抛出不同的异常。通过拦截异常我们就可以给用户反馈合适的错误信息。</p>

<h4>记住用户的登录状态</h4>

<p>登录成功，我们必须让应用程序记住用户的登录状态，不然用户一刷新页面，又变成未登录了。在
QeePHP
自动生成的应用程序对象中，已经提供了一系列方法来完成这项工作：</p>

<table>
	<thead>
		<tr>
			<th>方法名</th>

			<th>用途</th>
		</tr>
	</thead>

	<tbody>
		<tr>
			<th>changeCurrentUser</th>

			<td>将用户数据保存到 session 中</td>
		</tr>

		<tr>
			<th>currentUser()</th>

			<td>获取保存在 session 中的用户数据</td>
		</tr>

		<tr>
			<th>currentUserRoles()</th>

			<td>获取 session 中用户信息包含的角色</td>
		</tr>

		<tr>
			<th>cleanCurrentUser()</th>

			<td>从 session 中清除用户数据</td>
		</tr>
	</tbody>
</table>

<p>所以要让应用程序记住用户的登录状态，调用
changeCurrentUser() 方法就可以了。</p>

<h4>添加登录需要的视图</h4>

<p>我们还没有添加登录页面需要的视图，不过由于登录和注册的页面内容完全一样，所以我们直接重用注册的视图即可。在
actionLogin() 方法最后添加一行代码：</p>

<pre class="php code"><span class="re0">$this</span><span
class="sy0">-&gt;</span>_viewname <span class="sy0">=</span> <span
class="st_h">'register'</span><span class="sy0">;</span></pre>

<p>这行代码告诉 QeePHP 使用 register
动作的视图。修改好后，访问 <a
href="http://localhost/todo/public/index.php?controller=users&amp;action=login">http://localhost/todo/public/index.php?controller=users&action=login</a>
测试一下登录功能吧。</p>

<div class="figure"><img src="images/users-login-01.png" alt="" />
	<p>登录验证</p>
</div>

<h2>实现注销</h2>

<p>既然有登录，就有注销，在 users 控制器新增方法
actionLogout()：</p>

<pre class="php code"><span class="kw2">function</span> actionLogout<span
class="br0">&#40;</span><span class="br0">&#41;</span>
<span
class="br0">&#123;</span>
    <span
class="co1">// 清除当前用户的登录信息</span>
    <span
class="re0">$this</span><span class="sy0">-&gt;</span>_app<span
class="sy0">-&gt;</span><span class="me1">cleanCurrentUser</span><span
class="br0">&#40;</span><span class="br0">&#41;</span><span
class="sy0">;</span>
    <span
class="co1">// 重定向浏览器</span>
    <span
class="kw1">return</span> <span class="re0">$this</span><span
class="sy0">-&gt;</span>_redirect<span class="br0">&#40;</span>url<span
class="br0">&#40;</span><span class="st_h">'default/index'</span><span
class="br0">&#41;</span><span class="br0">&#41;</span><span
class="sy0">;</span>
<span class="br0">&#125;</span></pre>

<h2>在用户界面上显示登录状态</h2>

<p>现在注册、登录和注销功能都做好了，但是用户界面上还没有相应的链接，而且也没有显示出用户的登录状态。</p>

<p>在编码之前，我们可以打开 app/view/_layouts/default_layout.php
文件来看看。可以看到其中有一行：</p>

<pre class="php code"><span class="kw2">&lt;?php</span> <span
class="re0">$this</span><span class="sy0">-&gt;</span>_element<span
class="br0">&#40;</span><span class="st_h">'sidebar'</span><span
class="br0">&#41;</span><span class="sy0">;</span> <span
class="kw2">?&gt;</span></pre>

<p>这行代码载入了一个名为 sidebar
的视图片段，显示出来的效果就是：</p>

<div class="figure"><img src="images/users-login-02.png" alt="" />
	<p>侧边栏视图片段</p>
</div>

<p>在视图中，可以用“$this-&gt;_element(片段名)”来载入一个视图片段，该视图片段存储为“app/view/_elements/片段名_element.php”文件。例如“sidebar”视图片段对应的文件名就是“app/view/_elements/sidebar_element.php”。</p>

<p>视图片段通常用于一些在各个页面都会重复出现的内容，例如页头、页脚等。合理使用视图片段可以极大减少视图中存在的重复代码。不过视图片段并不能够处理自己需要的数据，所以像显示当前用户登录信息这样的需求，我们需要使用用户界面控件。</p>

<h4>用户界面组件化</h4>

<p>在前面的章节中，看到如何通过视图的继承和区块来分拆用户界面，最终获得各个可以重用的部分。而用户界面控件就是一种更强大的手段，可以将用户界面上的一个独立区域封装起来。</p>

<p>这个区域不但有自己的表现，还可以有自己的数据。例如“最新10个回复”这个控件可以自己查询最新的
10 个回复数据，然后载入自己的视图来构造 HTML
代码。而在传统开发模式中，如果某个页面需要“最新10个回复”这个区域，就必须在该页面对应的控制器动作中查询需要的数据。最终，各个控制器动作中都重复着相同的代码。</p>

<p>QeePHP
利用用户界面控件机制，彻底解决了这个问题，完全符合 DRY
原则。现在我们来把 sidebar
这个视图片段转换为一个用户界面控件。</p>

<p>用户界面通常由控件类和控件视图两部分组成。控件类都放置在
app/control/ 目录中。我们现在新建文件 app/control/sidebar.php
文件，内容如下：</p>

<pre class="php code"><span class="kw2">class</span> Control_Sidebar <span
class="kw2">extends</span> QUI_Control_Abstract
<span
class="br0">&#123;</span>
    <span class="kw2">function</span> render<span
class="br0">&#40;</span><span class="br0">&#41;</span>
    <span
class="br0">&#123;</span>
        <span
class="co1">// 从对象注册表中查询 app 对象</span>
        <span
class="re0">$app</span> <span class="sy0">=</span> Q<span
class="sy0">::</span><span class="me2">registry</span><span
class="br0">&#40;</span><span class="st_h">'app'</span><span
class="br0">&#41;</span><span class="sy0">;</span>
        <span
class="co1">// 取得当前用户的信息</span>
        <span
class="re0">$this</span><span class="sy0">-&gt;</span>_view<span
class="br0">&#91;</span><span class="st_h">'current_user'</span><span
class="br0">&#93;</span> <span class="sy0">=</span> <span
class="re0">$app</span><span class="sy0">-&gt;</span><span
class="me1">currentUser</span><span class="br0">&#40;</span><span
class="br0">&#41;</span><span class="sy0">;</span>
        <span
class="co1">// 渲染视图并返回结果</span>
        <span
class="kw1">return</span> <span class="re0">$this</span><span
class="sy0">-&gt;</span>_fetchView<span class="br0">&#40;</span><span
class="kw3">dirname</span><span class="br0">&#40;</span><span
class="kw2">__FILE__</span><span class="br0">&#41;</span> <span
class="sy0">.</span> <span class="st_h">'/sidebar_view'</span><span
class="br0">&#41;</span><span class="sy0">;</span>
    <span
class="br0">&#125;</span>
<span class="br0">&#125;</span></pre>

<p>然后将文件 app/view/_elements/sidebar_element.php 移动到 app/control/
目录，并改名为
sidebar_view.php。打开该文件，在适当位置添加如下内容：</p>

<pre class="php code"><span class="kw2">&lt;?php</span> <span
class="kw1">if</span> <span class="br0">&#40;</span><span
class="kw3">empty</span><span class="br0">&#40;</span><span
class="re0">$current_user</span><span class="br0">&#41;</span><span
class="br0">&#41;</span><span class="sy0">:</span> <span
class="kw2">?&gt;</span>
&nbsp;
    &lt;li&gt;
      &lt;h3&gt;用户登录&lt;/h3&gt;
      &lt;ul class=&quot;links&quot;&gt;
        &lt;li&gt;&lt;a href=&quot;<span
class="kw2">&lt;?php</span> <span class="kw3">echo</span> url<span
class="br0">&#40;</span><span class="st_h">'users/login'</span><span
class="br0">&#41;</span><span class="sy0">;</span> <span
class="kw2">?&gt;</span>&quot;&gt;登录&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;<span
class="kw2">&lt;?php</span> <span class="kw3">echo</span> url<span
class="br0">&#40;</span><span class="st_h">'users/register'</span><span
class="br0">&#41;</span><span class="sy0">;</span> <span
class="kw2">?&gt;</span>&quot;&gt;注册新用户&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
&nbsp;
<span
class="kw2">&lt;?php</span> <span class="kw1">else</span><span
class="sy0">:</span> <span
class="kw2">?&gt;</span>
&nbsp;
    &lt;li&gt;
      &lt;h3&gt;欢迎：<span
class="kw2">&lt;?php</span> <span class="kw3">echo</span> h<span
class="br0">&#40;</span><span class="re0">$current_user</span><span
class="br0">&#91;</span><span class="st_h">'username'</span><span
class="br0">&#93;</span><span class="br0">&#41;</span><span
class="sy0">;</span> <span
class="kw2">?&gt;</span>&lt;/h3&gt;
      &lt;ul class=&quot;links&quot;&gt;
        &lt;li&gt;用户名：<span
class="kw2">&lt;?php</span> <span class="kw3">echo</span> h<span
class="br0">&#40;</span><span class="re0">$current_user</span><span
class="br0">&#91;</span><span class="st_h">'username'</span><span
class="br0">&#93;</span><span class="br0">&#41;</span><span
class="sy0">;</span> <span
class="kw2">?&gt;</span>&lt;/li&gt;
        &lt;li&gt;用户ID：<span
class="kw2">&lt;?php</span> <span class="kw3">echo</span> <span
class="br0">&#40;</span><span class="re0">$current_user</span><span
class="br0">&#91;</span><span class="st_h">'id'</span><span
class="br0">&#93;</span><span class="br0">&#41;</span><span
class="sy0">;</span> <span
class="kw2">?&gt;</span>&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;<span
class="kw2">&lt;?php</span> <span class="kw3">echo</span> url<span
class="br0">&#40;</span><span class="st_h">'users/changepasswd'</span><span
class="br0">&#41;</span><span class="sy0">;</span> <span
class="kw2">?&gt;</span>&quot;&gt;修改密码&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;<span
class="kw2">&lt;?php</span> <span class="kw3">echo</span> url<span
class="br0">&#40;</span><span class="st_h">'users/logout'</span><span
class="br0">&#41;</span><span class="sy0">;</span> <span
class="kw2">?&gt;</span>&quot;&gt;注销&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
&nbsp;
<span
class="kw2">&lt;?php</span> <span class="kw1">endif</span><span
class="sy0">;</span> <span class="kw2">?&gt;</span></pre>

<p>最后，修改 app/view/_layouts/default_layout.php 文件，将：</p>

<pre class="php code"><span class="kw2">&lt;?php</span> <span
class="re0">$this</span><span class="sy0">-&gt;</span>_element<span
class="br0">&#40;</span><span class="st_h">'sidebar'</span><span
class="br0">&#41;</span><span class="sy0">;</span> <span
class="kw2">?&gt;</span></pre>

<p>改为：</p>

<pre class="php code"><span class="kw2">&lt;?php</span> <span
class="re0">$this</span><span class="sy0">-&gt;</span>_control<span
class="br0">&#40;</span><span class="st_h">'sidebar'</span><span
class="br0">&#41;</span><span class="sy0">;</span> <span
class="kw2">?&gt;</span></pre>

<p>刷新浏览器，并尝试登录和注册，观察侧边栏的变化：</p>

<div class="figure"><img src="images/users-login-03.png" alt="" />
	<p>未登录状态的侧边栏</p>
</div>

<div class="figure"><img src="images/users-login-04.png" alt="" />
	<p>登录状态的侧边栏</p>
</div>

<p>$Id: users-login.texy 2295 2009-03-10 07:48:18Z dualface $</p>

  </div>

  <div class="guide-footer">

    <table border="0" width="100%">
      <tr>
        <td align="left" width="200">
                    &laquo;
          <a href="node-users-register-code.html">用户注册（功能实现）</a>
          
        </td>

        <td align="center">
          本章：<a href="node-users.html">实现用户功能</a>
          <br />
          <a href="index.html">返回索引页</a>
        </td>

        <td align="right" width="200">
                    <a href="node-users-change-passwd.html">修改密码</a> 
          &raquo;
                  </td>
      </tr>
    </table>

  </div>

</div>


</div>

</body>
</html>


